#include "XMPPConnection.h"
#include <ConnectionConfiguration.h>
#include <PacketReader.h>
#include <PacketWriter.h>
#include <Roster.h>
#include <AccountManager.h>
#include <ChatManager.h>
#include <PacketListener.h>
#include <PacketInterceptor.h>
#include <PacketCollector.h>
#include <ConnectionListener.h>
#include <SASLAuthentication.h>
#include <ConnectionCreationListener.h>
#include <NonSASLAuthentication.h>

#include <filter/PacketFilter.h>

#include <packet/Packet.h>
#include <packet/Presence.h>

#include <util/socket/TCPSocket.h>
#include <util/zipstream.h>
#include <util/StringUtils.h>


int XMPPConnection::connectionCounter = 0; 


XMPPConnection::XMPPConnection(std::string & serviceName) :
	wasAuthenticated(false),
	anonymous(false),
	usingTLS(false),
	saslAuthentication(new SASLAuthentication(this)),
	socket(new TCPSocket(socketHandler))
{
    // Create the configuration for this new connection
    ConnectionConfiguration * config = new ConnectionConfiguration(serviceName);
    config->setCompressionEnabled(false);
    config->setSASLAuthenticationEnabled(true);
    //TODO config->setDebuggerEnabled(DEBUG_ENABLED);
    this->configuration = config;
}

XMPPConnection::XMPPConnection(ConnectionConfiguration * config) : 
	socket(new TCPSocket(socketHandler)),
	saslAuthentication(new SASLAuthentication(this)),
	configuration(config)
{
}

XMPPConnection::~XMPPConnection() {
	delete socket;
}

void XMPPConnection::login(std::string & username, std::string & password)
{
	std::string resource("");
	login(username, password, resource);
}

void XMPPConnection::login(std::string & username, std::string & password, std::string & resource)
{
	login(username, password, resource, true);
}

void XMPPConnection::login(std::string username, std::string password, std::string resource, bool sendPresence)
{
	if (!isConnected()) {
    }
    if (authenticated) {
    }
	// Do partial version of nameprep on the username.
    this->user = username;

	std::string response;
    if (configuration->isSASLAuthenticationEnabled() &&
    	saslAuthentication->hasNonAnonymousAuthentication()) {
        // Authenticate using SASL
        response = saslAuthentication->authenticate(username, password, resource);
    } else {
        // Authenticate using Non-SASL
		NonSASLAuthentication nonSASLAuthentication(this);
        response = nonSASLAuthentication.authenticate(username, password, resource);
    }

    // Set the user.
    if (!response.empty()) {
    	this->user = response;
        // Update the serviceName with the one returned by the server
        this->serviceName = StringUtils::parseServer(response);
    } else {
        this->user = username + "@" + this->serviceName;
        if (!resource.empty()) {
            this->user += "/" + resource;
        }
    }

    // If compression is enabled then request the server to use stream compression
    if (configuration->isCompressionEnabled()) {
    	useCompression();
    }

    // Create the roster if it is not a reconnection.
    if (this->roster == NULL) {
    	this->roster = new Roster(this);
    }
    roster->reload();

    // Set presence to online.
    if (sendPresence) {
    	packetWriter->sendPacket(new Presence(Presence::TYPE_AVAILABLE));
    }

    // Indicate that we're now authenticated.
    authenticated = true;
    anonymous = false;

    // Stores the autentication for future reconnection
    this->getConfiguration()->setLoginInfo(username, password, resource, sendPresence);

    // If debugging is enabled, change the the debug window title to include the
    // name we are now logged-in as.
    // If DEBUG_ENABLED was set to true AFTER the connection was created the debugger
    // will be null
	/*
    if (configuration->isDebuggerEnabled() && debugger != NULL) {
    	debugger->userHasLogged(user);
    }
	*/
}

void XMPPConnection::loginAnonymously() {
    if (!connected) {
    }
    if (authenticated) {
    }

	std::string response;
    if (configuration->isSASLAuthenticationEnabled() &&
    	saslAuthentication->hasAnonymousAuthentication()) {
       	response = saslAuthentication->authenticateAnonymously();
    } else {
        // Authenticate using Non-SASL
		NonSASLAuthentication nonSASLAuthentication(this);
        response = nonSASLAuthentication.authenticateAnonymously();
    }

    // Set the user value.
    this->user = response;
    // Update the serviceName with the one returned by the server
    this->serviceName = StringUtils::parseServer(response);

    // If compression is enabled then request the server to use stream compression
    if (configuration->isCompressionEnabled()) {
    	useCompression();
    }

    // Anonymous users can't have a roster.
    roster = NULL;

    // Set presence to online.
    packetWriter->sendPacket(new Presence(Presence::TYPE_AVAILABLE));

    // Indicate that we're now authenticated.
    authenticated = true;
    anonymous = true;

    // If debugging is enabled, change the the debug window title to include the
    // name we are now logged-in as.
    // If DEBUG_ENABLED was set to true AFTER the connection was created the debugger
    // will be null
	/*
    if (configuration->isDebuggerEnabled() && debugger != NULL) {
    	debugger->userHasLogged(user);
    }
	*/
}

Roster * XMPPConnection::getRoster() {
    if (roster == NULL || !roster->isInitialized() ) { //TODO init roster then return
    	return NULL;
    }
	return roster;
}

AccountManager * XMPPConnection::getAccountManager() {
    if (accountManager == NULL) {
    	accountManager = new AccountManager(this);
    }
    return accountManager;
}

ChatManager * XMPPConnection::getChatManager() {
    if (chatManager == NULL) {
    	chatManager = new ChatManager(this);
    }
    return chatManager;
}

bool XMPPConnection::isConnected() {
	return connected;
}

bool XMPPConnection::isSecureConnection() {
	return isUsingTLS();
}

bool XMPPConnection::isAuthenticated() {
	return authenticated;
}

bool XMPPConnection::isAnonymous() {
	return anonymous;
}

void XMPPConnection::shutdown(Presence * unavailablePresence) {
	// Set presence to offline.
	packetWriter->sendPacket(unavailablePresence);

	setWasAuthenticated(authenticated);
	authenticated = false;
	connected = false;

	packetReader->shutdown();
	packetWriter->shutdown();
	// Wait 150 ms for processes to clean-up, then shutdown.
	//	Thread.sleep(150);  TODO

	// Close down the readers and writers.
	reader->clear();
	writer->clear();

	socket->Close();
}

void XMPPConnection::disconnect() {
	disconnect(new Presence(Presence::TYPE_UNAVAILABLE));
}

void XMPPConnection::disconnect(Presence * unavailablePresence) {
    // If not connected, ignore this request.
    if (packetReader == NULL || packetWriter == NULL) {
    	return;
    }

    shutdown(unavailablePresence);
    
	if (roster != NULL) {
    	roster->cleanup();
        roster = NULL;
    }

   	wasAuthenticated = false;

    packetWriter->cleanup();
    packetWriter = NULL;
    packetReader->cleanup();
    packetReader = NULL;
}

void XMPPConnection::sendPacket(Packet * packet) {
	if (!connected) {
		//TODO
	}
	if (packet == NULL) {
		//TODO
	}
    packetWriter->sendPacket(packet);
}

void XMPPConnection::addPacketListener(PacketListener * packetListener, PacketFilter * packetFilter) {
	if (!connected) {
		//TODO throw new IllegalStateException("Not connected to server.");
	}
    packetReader->addPacketListener(packetListener, packetFilter);
}

void XMPPConnection::removePacketListener(PacketListener * packetListener) {
	packetReader->removePacketListener(packetListener);
}

void XMPPConnection::addPacketWriterListener(PacketListener * packetListener, PacketFilter * packetFilter) {
	if (!connected) {
    	//TODO throw new IllegalStateException("Not connected to server.");
    }
    packetWriter->addPacketListener(packetListener, packetFilter);
}

void XMPPConnection::removePacketWriterListener(PacketListener * packetListener) {
	packetWriter->removePacketListener(packetListener);
}

void XMPPConnection::addPacketWriterInterceptor(PacketInterceptor * packetInterceptor, PacketFilter * packetFilter) {
    if (!connected) {
        //TODO throw new IllegalStateException("Not connected to server.");
    }
    packetWriter->addPacketInterceptor(packetInterceptor, packetFilter);
}

void XMPPConnection::removePacketWriterInterceptor(PacketInterceptor * packetInterceptor) {
	packetWriter->removePacketInterceptor(packetInterceptor);
}

PacketCollector * XMPPConnection::createPacketCollector(PacketFilter * packetFilter) {
    return packetReader->createPacketCollector(packetFilter);
}

void XMPPConnection::addConnectionListener(ConnectionListener * connectionListener) {
    if (!connected) {
    	//TODO throw new IllegalStateException("Not connected to server.");
    }
    if (connectionListener == NULL) {
    	return;
    }
    if (!packetReader->hasConnectionListener(connectionListener)) {
    	packetReader->addConnectionListener(connectionListener);
    }
}

void XMPPConnection::removeConnectionListener(ConnectionListener * connectionListener) {
    packetReader->removeConnectionListener(connectionListener);
}

void XMPPConnection::addConnectionCreationListener( ConnectionCreationListener * connectionCreationListener) {
    connectionEstablishedListeners.push_back(connectionCreationListener);
}

void XMPPConnection::removeConnectionCreationListener( ConnectionCreationListener * listener) {
	std::vector<ConnectionCreationListener*>::iterator it = std::find(connectionEstablishedListeners.begin(), 
			connectionEstablishedListeners.end(), listener); 
	delete *it;
	connectionEstablishedListeners.erase(it);
}

void XMPPConnection::connectUsingConfiguration(ConnectionConfiguration * config) {
    host = config->getHost();
    port = config->getPort();
    socket->Open(host, port);
        
	serviceName = config->getServiceName();
    initConnection();
}

void XMPPConnection::initConnection() {
    bool isFirstInitialization = packetReader == NULL || packetWriter == NULL ;
    if (!isFirstInitialization) {
    	usingCompression = false;
    }

    // Set the reader and writer instance variables
    initReaderAndWriter();

    if (isFirstInitialization) {
        packetWriter = new PacketWriter(this);
        packetReader = new PacketReader(this);

        // If debugging is enabled, we should start the thread that will listen for
        // all packets and then log them.
		/*
        if (configuration.isDebuggerEnabled()) {
            packetReader.addPacketListener(debugger.getReaderListener(), null);
            if (debugger.getWriterListener() != null) {
                packetWriter.addPacketListener(debugger.getWriterListener(), null);
            }
        }
		*/
    } else {
        packetWriter->init();
        packetReader->init();
    }

    // Start the packet writer. This will open a XMPP stream to the server
    packetWriter->startup();
    // Start the packet reader. The startup() method will block until we
    // get an opening stream packet back from server.
    packetReader->startup();

    // Make note of the fact that we're now connected.
    connected = true;

    // Start keep alive process (after TLS was negotiated - if available)
    packetWriter->startKeepAliveProcess();


    if (isFirstInitialization) {
    	// Notify listeners that a new connection has been established
        for (std::vector<ConnectionCreationListener*>::iterator it = connectionEstablishedListeners.begin();
				it != connectionEstablishedListeners.end(); ++it) {
        	(*it)->connectionCreated(this);
        }
    } else {
        packetReader->notifyReconnection();
    }

}

void XMPPConnection::initReaderAndWriter() {
	if (usingCompression) {
		socket->usingZlibCompression();
	}
	reader = socket->getIStream();
	writer = socket->getOStream();
}

bool XMPPConnection::isUsingTLS() {
    return usingTLS;
}

SASLAuthentication * XMPPConnection::getSASLAuthentication() {
    return saslAuthentication;
}

ConnectionConfiguration * XMPPConnection::getConfiguration() {
    return configuration;
}

void XMPPConnection::startTLSReceived(bool required) {
	if (required && configuration->getSecurityMode() == ConnectionConfiguration::DISABLED) {
		std::string s("TLS required by server but not allowed by connection configuration");
        packetReader->notifyConnectionError(s);
        return;
    }

    if (configuration->getSecurityMode() == ConnectionConfiguration::DISABLED) {
    	// Do not secure the connection using TLS since TLS was disabled
    	return;
    }
	*writer << "<starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\"/>";
    writer->flush();
}

void XMPPConnection::proceedTLSReceived() {
	//TODO
}

void XMPPConnection::setAvailableCompressionMethods(std::vector<std::string> & methods) {
	compressionMethods = methods;
}

bool XMPPConnection::hasAvailableCompressionMethod(std::string method) {
	if(!compressionMethods.empty()) {
		std::vector<std::string>::const_iterator it = std::find(
				compressionMethods.begin(), compressionMethods.end(), method);  
  		
		if(it != compressionMethods.end()) {
			return true;
		}
  	}
	return false;
}

bool XMPPConnection::isUsingCompression() {
    return usingCompression;
}

bool XMPPConnection::useCompression() {
    // If stream compression was offered by the server and we want to use
    // compression then send compression request to the server
    if (authenticated) {
    	//throw new IllegalStateException("Compression should be negotiated before authentication.");
    }
        
	if (hasAvailableCompressionMethod("zlib")) {
        requestStreamCompression();
        return usingCompression;
    }
}

void XMPPConnection::requestStreamCompression() {
    *writer << "<compress xmlns='http://jabber.org/protocol/compress'>";
    *writer << "<method>zlib</method></compress>";
	writer->flush();
}

void XMPPConnection::startStreamCompression() {
	// Secure the plain connection
    usingCompression = true;
    // Initialize the reader and writer with the new secured version
    initReaderAndWriter();

    // Set the new  writer to use
    packetWriter->setStream(writer);
    // Send a new opening stream to the server
    packetWriter->openStream();
}

void XMPPConnection::connect() {
    // Stablishes the connection, readers and writers
    connectUsingConfiguration(configuration);
    // Automatically makes the login if the user was previouslly connected successfully
    // to the server and the connection was terminated abruptly
    if (connected && wasAuthenticated) {
        // Make the login
        if (isAnonymous()) {
        	// Make the anonymous login
            loginAnonymously();
        } else {
            login(configuration->getUsername(), configuration->getPassword(),
            configuration->getResource(), configuration->isSendPresence());
        }
    }
}

void XMPPConnection::setWasAuthenticated(bool wasAuthenticated) {
	if (!wasAuthenticated) {
		wasAuthenticated = wasAuthenticated;
	}
}
